package com.minelx.datamicroscope.core.query;

import com.minelx.datamicroscope.core.condition.Condition;
import com.minelx.datamicroscope.core.condition.KeyOperandCondition;
import com.minelx.datamicroscope.core.values.CommonField;
import com.minelx.datamicroscope.core.values.CommonTable;
import com.minelx.sqlstructures.statements.SelectBuilder;
import com.minelx.sqlstructures.statements.clause.Clauses;
import com.minelx.sqlstructures.statements.clause.Field;

import java.util.ArrayList;
import java.util.List;

import static com.minelx.datamicroscope.core.condition.Operand.gt;
import static java.lang.String.format;

public class TrickleQueryBuilder {

	private final String databaseIndex;

	private final CommonField primaryField;

	private final SelectBuilder builder;

	private final List<AliasedKey> queriedAliasedKeys;

	private boolean markAddedPrimaryField;

	private TrickleQueryBuilder(String databaseIndex, String databaseName, String tableName, CommonField primaryField) {
		this.databaseIndex = databaseIndex;
		this.primaryField = primaryField;

		builder = SelectBuilder.create(databaseName + "." + tableName);
		// preset primary field collect
		builder.select().add(primaryField.toSqlField());
		markAddedPrimaryField = false;

		// prepare field locates
		queriedAliasedKeys = new ArrayList<>();
	}

	public void addField(AliasedKey added) {
		Field addedSqlField = added.asSqlField();
		// mark queried field locate
		queriedAliasedKeys.add(added);

		// mark primary field added flag
		if (addedSqlField.equals(primaryField.toSqlField())) {
			markAddedPrimaryField = true;
			return;
		}

		builder.select().add(asSqlField(added));
	}

	private Field asSqlField(AliasedKey added) {
		// FIXME 2022/4/8 wait for fixing.
		Field addedSqlField = added.asSqlField();
		String typeOfAddedField = added.getKey().structure().getType();
		if (!typeOfAddedField.contains("char") && !typeOfAddedField.contains("text")) { // not varchar or text
			return addedSqlField;
		}
		// text type
		String originalExpr = addedSqlField.getName();
		// we should do some preprocessing to some kinds of column type
		String preprocessedExpr = capWithQuote(originalExpr);
		return Field.as(preprocessedExpr, addedSqlField.representName());
	}

	public void addCondition(Condition added) {
		builder.where().add(added.sqlText());
		// order by primary field if depended on index which is not primary
		if (added.containedFields().stream().anyMatch(index -> !index.equals(primaryField))) {
			builder.orderBy(Clauses.orderBy(primaryField.name()));
		}
	}

	public TrickleQuery buildQuery() {
		SelectBuilder result = builder.copy();
		// preset primary field condition
		result.where().add(KeyOperandCondition.of(primaryField, gt("{{BVAL}}")).sqlText());
		// set limitation
		result.limit(Clauses.limit(1000));
		return new TrickleQuery(result, queriedAliasedKeys, primaryField, databaseIndex, markAddedPrimaryField);
	}

	public static TrickleQueryBuilder create(String databaseIndex, String databaseName, String tableName, CommonField primaryField) {
		return new TrickleQueryBuilder(databaseIndex, databaseName, tableName, primaryField);
	}

	public static TrickleQueryBuilder query(CommonTable commonTable) {
		return create(
				commonTable.commonDatabase().index(),
				commonTable.commonDatabase().name(),
				commonTable.name(),
				commonTable.primaryKey());
	}

	private static String capWithQuote(String capped) {
		return format("CONCAT('\"', %s, '\"')", capped);
	}
}
